import os
import logging
from logging.handlers import TimedRotatingFileHandler
from flask import Flask, json, Response
from flask_restplus import Api
from werkzeug.utils import find_modules, import_string
from datetime import datetime, time
from decimal import Decimal
from config import SystemEnvironment
from celery import Celery

authorization = {
    'apiKey': {
        'type': 'apiKey',
        'in': 'header',
        'name': 'token'
    }
}
api = Api(doc='/swagger-ui.html', title='test', authorizations=authorization)


class CJSONEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return o.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(o, time):
            return o.strftime('%H:%M:%S')
        elif isinstance(o, Decimal):
            return o.quantize(Decimal('0.00'))
        elif isinstance(o, bytes):
            return o.decode('utf-8')
        return json.JSONEncoder.default(self, 0)


class JSONResult(object):
    def __init__(self, value, status=200):
        self.value = value
        self.status = status

    def to_response(self):
        return Response(json.dumps(self.value, cls=CJSONEncoder), status=self.status, mimetype='application/json')


class SystemFlask(Flask):

    def make_response(self, rv):
        if isinstance(rv, dict):
            if 'code' not in rv:
                rv['code'] = 200
            rv = JSONResult(rv)
        if isinstance(rv, JSONResult):
            return rv.to_response()
        return Flask.make_response(self, rv)


def init_logging(app):
    LEVELS = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
    }
    log_dir = os.path.join(app.config['LOG_PATH'])
    log_file = os.path.join(app.config['LOG_PATH'], app.config['LOG_FILENAME'])
    if not os.path.exists(log_dir):
        os.mkdir(log_dir)
    log_level = LEVELS.get(app.config['LOG_LEVEL'].lower(), 'info')

    formatter = logging.Formatter('%(asctime)s [%(pathname)s] [%(funcName)s] %(levelname)s [%(lineno)d] %(message)s')
    rotate_handler = TimedRotatingFileHandler(log_file, "D", 1, 30)
    rotate_handler.suffix = "%Y%m%d.log"
    rotate_handler.setLevel(log_level)
    rotate_handler.setFormatter(formatter)

    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(log_level)
    stream_handler.setFormatter(formatter)

    app.logger.addHandler(rotate_handler)
    app.logger.addHandler(stream_handler)


def init_error_handler(app):
    from .errors import APIError
    from .errors import error_403_handler, error_404_handler, error_429_handler, error_500_handler, error_handler
    from .errors import error_405_handler
    app.register_error_handler(403, error_403_handler)
    app.register_error_handler(404, error_404_handler)
    app.register_error_handler(405, error_405_handler)
    app.register_error_handler(429, error_429_handler)
    app.register_error_handler(500, error_500_handler)
    app.register_error_handler(APIError, error_handler)


def init_hooks(app):
    from .interceptors import before_request_handler, after_request_handler
    app.before_request(before_request_handler)
    app.after_request(after_request_handler)


def register_blueprints(app):
    modules = find_modules('core.views', recursive=True)
    for name in modules:
        module = import_string(name)
        if hasattr(module, 'bp'):
            app.register_blueprint(module.bp)


def init_db(app):
    from .db import db
    db.init_app(app)


def make_celery(app=None):
    app = app or create_app()
    celery = Celery('system', broker=app.config.get('BROKER_URL'), backend=app.config.get('RESULT_BACKEND'))
    celery.conf.update(app.config.get('CELERY_CONFIG') or {})
    TaskBase = celery.Task

    class ContextTask(TaskBase):
        abstract = True

        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)

    celery.Task = ContextTask
    return celery


def create_app():
    app = SystemFlask("System")
    app.config.setdefault('RESTFUL_JSON', {
        'cls': CJSONEncoder
    })
    # 系统环境变量
    SystemEnvironment(app)
    init_logging(app)
    init_error_handler(app)
    init_hooks(app)
    init_db(app)
    api.init_app(app)
    register_blueprints(app)
    make_celery(app)
    return app
